// EditorContext.cs
//
// Copyright (c) 2007 Mike Strobel
//
// This source code is subject to the terms of the Microsoft Reciprocal License (Ms-RL).
// For details, see <http://www.opensource.org/licenses/ms-rl.html>.
//
// All other rights reserved.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Xml;
using System.Xml.Linq;

using Supremacy.Buildings;
using Supremacy.Economy;
using Supremacy.Entities;
using Supremacy.Game;
using Supremacy.Orbitals;
using Supremacy.Resources;
using Supremacy.Tech;
using Supremacy.Text;
using Supremacy.Types;
using Supremacy.Universe;

namespace Supremacy.Editor
{
    [Serializable]
    public sealed class EditorContext
    {
        #region Static Members
        public static readonly string StringTablePath;

        static EditorContext()
        {
            string root = Environment.CurrentDirectory;
            StringTablePath = root + @"\Resources\Strings\en.txt";
            s_transactionLock = new object();
        }
        #endregion

        #region Fields
        public readonly string WorkingDirectory;

        private static readonly object s_transactionLock;

        private StringTableDocument _stringTable;
        private TechDatabase _techDatabase;
        private RaceDatabase _races;
        private CivDatabase _civilizations;
        private TechTree _defaultTechTree;
        private IDictionary<string, TechTree> _civTechTrees;
        private HomeSystemsDatabase _homeSystems;
        private HomeSystemsDatabase _oldHomeSystems;
        #endregion

        public event AddingNewEventHandler AddingNew;

        internal void OnAddingNew(object newObject)
        {
            if (AddingNew != null)
                AddingNew(this, new AddingNewEventArgs(newObject));
        }

        #region Properties
        public StringTableDocument StringTable
        {
            get { return _stringTable; }
        }

        public TechDatabase TechDatabase
        {
            get { return _techDatabase; }
        }

        public RaceDatabase Races
        {
            get { return _races; }
        }

        public CivDatabase Civilizations
        {
            get { return _civilizations; }
        }

        public TechTree DefaultTechTree
        {
            get { return this._defaultTechTree; }
        }

        public IDictionary<string, TechTree> CivTechTrees
        {
            get { return _civTechTrees; }
        }

        public HomeSystemsDatabase HomeSystems
        {
            get { return _homeSystems; }
        }
        #endregion

        //private void RollbackTransaction()
        //{
        //    lock (TransactionLock)
        //    {
        //        if (_transactionScope != null)
        //        {
        //            try { _transactionScope.Dispose(); }
        //            catch { }
        //            finally { _transactionScope = null; }
        //        }
        //    }
        //}

        //private void CommitTransaction()
        //{
        //    lock (TransactionLock)
        //    {
        //        if (_transactionScope != null)
        //        {
        //            try { _transactionScope.Complete(); }
        //            catch {}
        //            try { _transactionScope.Dispose(); }
        //            catch {}
        //        }
        //        _transactionScope = null;
        //        EnsureTransaction();
        //    }
        //}


        //private bool EnsureTransaction()
        //{
        //    lock (TransactionLock)
        //    {
        //        if (Transaction.Current != null)
        //        {
        //            if (Transaction.Current.TransactionInformation.Status == TransactionStatus.Active)
        //                return true;
        //            RollbackTransaction();
        //        }
        //        try
        //        {
        //            _transactionScope = new TransactionScope(
        //                TransactionScopeOption.Required/*,
        //                new TransactionOptions()
        //                {
        //                    IsolationLevel = System.Transactions.IsolationLevel.Unspecified,
        //                    Timeout = new TimeSpan(Int32.MaxValue)
        //                },
        //                EnterpriseServicesInteropOption.Full*/);
        //            if ((ResourceManager.Database.Connection.State == ConnectionState.Closed)
        //                || (ResourceManager.Database.Connection.State == ConnectionState.Broken))
        //            {
        //                ResourceManager.Database.Connection.Open();
        //            }
        //            //ResourceManager.Database.Connection.EnlistTransaction(_transaction);
        //            return true;
        //        }
        //        catch
        //        {
        //            RollbackTransaction();
        //            return false;
        //        }
        //    }
        //}

        #region Constructors
        internal EditorContext()
        {
            //EnsureTransaction();
                
            WorkingDirectory = Environment.CurrentDirectory;

            if (GameContext.Current == null)
                GameContext.Push(new GameContext());

            GameContext.Current.Tables = GameTables.Load();

            try
            {
                ResourceManager.Initialize();
            }
            catch (Exception e)
            {
                throw new SupremacyException("Could not initialize resource manager.", e);
            }
            try
            {
                _stringTable = StringTableReader.Read(StringTablePath);
            }
            catch (Exception e)
            {
                throw new SupremacyException("Could not load string table.", e);
            }
            try
            {
                _techDatabase = TechDatabase.Load();
                foreach (var design in _techDatabase)
                {
                    design.ObjectString = ResourceManager.Database.FindObjectString(design.Key);
                }
                GameContext.Current.TechDatabase = _techDatabase;
            }
            catch (Exception e)
            {
                throw new SupremacyException("Could not load tech database.", e);
            }
            try
            {
                _races = RaceDatabase.Load();
                GameContext.Current.Races = _races;
            }
            catch (Exception e)
            {
                throw new SupremacyException("Could not load race database.", e);
            }
            try
            {
                _civilizations = CivDatabase.Load();
                GameContext.Current.Civilizations = _civilizations;
                foreach (Civilization civ in _civilizations)
                {
                    civ.Race = _races[civ.RaceID];
                }
            }
            catch (Exception e)
            {
                throw new SupremacyException("Could not load civilization database.", e);
            }
            try
            {
                XDocument xDoc = XDocument.Load("Resources/Data/TechTrees.xml");
                IDictionary<string, int> designIdMap = _techDatabase.DesignIdMap;

                this._defaultTechTree = new TechTree();
                _civTechTrees = new Dictionary<string, TechTree>();

                this._defaultTechTree = new TechTree();

                foreach (TechObjectDesign design in _techDatabase)
                {
                    if (design.IsUniversallyAvailable)
                        this._defaultTechTree.Add(design);
                }

                if (xDoc.Root != null)
                {
                    foreach (var treeElement in xDoc.Root.Elements())
                    {
                        var civKey = (string)treeElement.Attribute("Civilization");
                        var tree = new TechTree();

                        if (civKey == null)
                            continue;
                        
                        _civTechTrees[civKey] = tree;

                        foreach (var facilityElement in treeElement.Descendants("ProductionFacility"))
                        {
                            var designKey = (string)facilityElement;
                            if (String.IsNullOrEmpty(designKey))
                                continue;
                            int designId;
                            if (designIdMap.TryGetValue(designKey, out designId))
                                tree.Add(_techDatabase[designId]);
                        }
                        foreach (var buildingElement in treeElement.Descendants("Building"))
                        {
                            var designKey = (string)buildingElement;
                            if (String.IsNullOrEmpty(designKey))
                                continue;
                            int designId;
                            if (designIdMap.TryGetValue(designKey, out designId))
                                tree.Add(_techDatabase[designId]);
                        }
                        foreach (var shipElement in treeElement.Descendants("Ship"))
                        {
                            var designKey = (string)shipElement;
                            if (String.IsNullOrEmpty(designKey))
                                continue;
                            int designId;
                            if (designIdMap.TryGetValue(designKey, out designId))
                                tree.Add(_techDatabase[designId]);
                        }
                        foreach (var shipyardElement in treeElement.Descendants("Shipyard"))
                        {
                            var designKey = (string)shipyardElement;
                            if (String.IsNullOrEmpty(designKey))
                                continue;
                            int designId;
                            if (designIdMap.TryGetValue(designKey, out designId))
                                tree.Add(_techDatabase[designId]);
                        }
                        foreach (var stationElement in treeElement.Descendants("SpaceStation"))
                        {
                            var designKey = (string)stationElement;
                            if (String.IsNullOrEmpty(designKey))
                                continue;
                            int designId;
                            if (designIdMap.TryGetValue(designKey, out designId))
                                tree.Add(_techDatabase[designId]);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                throw new SupremacyException("Could not load tech trees.", e);
            }
            try
            {
                _homeSystems = HomeSystemsDatabase.Load();
                _oldHomeSystems = new HomeSystemsDatabase();
            }
            catch (Exception e)
            {
                throw new SupremacyException("Could not load civilization home systems.", e);
            }
        }
        #endregion

        public StarSystemDescriptor GetOrCreateHomeSystem(Civilization civ)
        {
            if (civ == null)
                throw new ArgumentNullException("civ");
            if (!_homeSystems.ContainsKey(civ.Key))
            {
                if (_oldHomeSystems.ContainsKey(civ.Key))
                {
                    _homeSystems[civ.Key] = _oldHomeSystems[civ.Key];
                }
                else
                {
                    _homeSystems[civ.Key] = new StarSystemDescriptor
                                            {
                                                Name = civ.HomeSystemName
                                            };
                    if (civ.CivilizationType != CivilizationType.MinorPower)
                        _homeSystems[civ.Key].AddBonus(SystemBonus.Dilithium);
                }
            }
            return _homeSystems[civ.Key];
        }

        public void ClearHomeSystem(Civilization civ)
        {
            if ((civ != null) && _homeSystems.ContainsKey(civ.Key))
            {
                _oldHomeSystems[civ.Key] = _homeSystems[civ.Key];
                _homeSystems.Remove(civ.Key);
            }
        }

        internal void Save()
        {
            lock (s_transactionLock)
            {
                try
                {
                    StringTableWriter.Write(StringTablePath, StringTable);
                    TechDatabase.Save();
                    Races.Save();
                    Civilizations.Save();
                    SaveTechTrees();
                    SaveHomeSystems();
                    ResourceManager.Database.SubmitChanges();
                }
                catch (Exception e)
                {
                    MessageBox.Show(
                        e.Message,
                        "Error",
                        MessageBoxButton.OK,
                        MessageBoxImage.Error);
                }
            }
        }

        private void SaveHomeSystems()
        {
            if (_homeSystems != null)
            {
                if (_civilizations != null)
                {
                    foreach (Civilization civ in _civilizations)
                    {
                        if (_homeSystems.ContainsKey(civ.Key)
                            && !String.IsNullOrEmpty(civ.HomeSystemName))
                        {
                            _homeSystems[civ.Key].Name = civ.HomeSystemName;
                        }
                    }
                }
                _homeSystems.Save();
            }
        }

        private void SaveTechTrees()
        {
            string fileName = Path.Combine(
                WorkingDirectory,
                @"Resources\Data\TechTrees.xml");

            XmlWriterSettings settings = new XmlWriterSettings
                                         {
                                             Encoding = Encoding.UTF8,
                                             Indent = true,
                                             IndentChars = "  ",
                                             CloseOutput = true,
                                             ConformanceLevel = ConformanceLevel.Document
                                         };

            using (XmlWriter writer = XmlWriter.Create(fileName, settings))
            {
                if (writer == null)
                    return;

                writer.WriteStartDocument();
                writer.WriteStartElement("TechTrees");

                foreach (string civId in _civTechTrees.Keys)
                {
                    TechTree civTechTree = _civTechTrees[civId];
                    if (civTechTree != null)
                    {
                        TechTree treeCopy = new TechTree();

                        foreach (TechObjectDesign design in civTechTree)
                        {
                            if (!design.IsUniversallyAvailable)
                                treeCopy.Add(design);
                        }

                        if (!treeCopy.IsEmpty)
                        {
                            writer.WriteStartElement("TechTree");
                            writer.WriteAttributeString("Civilization", civId);
                            WriteTechTreeItems(civTechTree, writer);
                            writer.WriteEndElement();   
                        }
                    }
                }
                
                writer.WriteEndElement();
                writer.WriteEndDocument();
            }
        }

        private static void WriteTechTreeItems(TechTree tree, XmlWriter writer)
        {
            if (tree == null)
                throw new ArgumentNullException("tree");
            if (writer == null)
                throw new ArgumentNullException("writer");

            writer.WriteStartElement("ProductionFacilities");
            foreach (ProductionFacilityDesign design in tree.ProductionFacilityDesigns)
            {
                if (!design.IsUniversallyAvailable)
                    writer.WriteElementString("ProductionFacility", design.Key);
            }
            writer.WriteEndElement();

            writer.WriteStartElement("Buildings");
            foreach (BuildingDesign design in tree.BuildingDesigns)
            {
                if (!design.IsUniversallyAvailable && !(design is ShipyardDesign))
                    writer.WriteElementString("Building", design.Key);
            }
            writer.WriteEndElement();

            writer.WriteStartElement("Ships");
            foreach (ShipDesign design in tree.ShipDesigns)
            {
                if (!design.IsUniversallyAvailable)
                    writer.WriteElementString("Ship", design.Key);
            }
            writer.WriteEndElement();

            writer.WriteStartElement("Shipyards");
            foreach (BuildingDesign design in tree.BuildingDesigns)
            {
                if (!design.IsUniversallyAvailable && (design is ShipyardDesign))
                    writer.WriteElementString("Shipyard", design.Key);
            }
            writer.WriteEndElement();

            writer.WriteStartElement("SpaceStations");
            foreach (StationDesign design in tree.StationDesigns)
            {
                if (!design.IsUniversallyAvailable)
                    writer.WriteElementString("SpaceStation", design.Key);
            }
            writer.WriteEndElement();
        }

        public void Close()
        {
            //RollbackTransaction();

            _civTechTrees.Clear();
            _techDatabase.Clear();
            _civilizations.Clear();
            _races.Clear();
            _homeSystems.Clear();
            _oldHomeSystems.Clear();

            GameContext.Current.Civilizations = null;
            GameContext.Current.Races = null;
            GameContext.Current.Tables = null;
            GameContext.Current.TechDatabase = null;

            _civilizations = null;
            _civTechTrees = null;
            _defaultTechTree = null;
            _homeSystems = null;
            _oldHomeSystems = null;
            _races = null;
            _stringTable = null;
            _techDatabase = null;

        }

        public void DeleteCivilization(Civilization civilization)
        {
            if (civilization == null)
                throw new ArgumentNullException("civilization");
            EditorWindow.Current.CloseDocumentWindowForItem(civilization);
            _civilizations.Remove(civilization);
            _civTechTrees.Remove(civilization.Key);
            _homeSystems.Remove(civilization.Key);
            _oldHomeSystems.Remove(civilization.Key);
        }

        public void DeleteRace(Race race)
        {
            if (race == null)
                throw new ArgumentNullException("race");
            foreach (var civBasedOnRace in _civilizations.Where(o => o.Race == race).ToList())
                DeleteCivilization(civBasedOnRace);
            EditorWindow.Current.CloseDocumentWindowForItem(race);
            _races.Remove(race.Key);
        }

        public void DeleteTechObjectDesign(TechObjectDesign design)
        {
            if (design == null)
                throw new ArgumentNullException("design");

            EditorWindow.Current.CloseDocumentWindowForItem(design);

            foreach (var otherDesign in _techDatabase)
            {
                otherDesign.UpgradableDesigns.Remove(design);
                otherDesign.ObsoletedDesigns.Remove(design);
                foreach (var prereqList in otherDesign.Prerequisites.ToArray())
                {
                    if (prereqList.Remove(otherDesign) && !prereqList.Any())
                        otherDesign.Prerequisites.Remove(prereqList);
                }
            }

            foreach (var civTechTree in _civTechTrees.Values)
                civTechTree.Remove(design);

            _techDatabase.Remove(design);

            ResourceManager.Database.ObjectStrings.DeleteOnSubmit(design.ObjectString);
        }
    }
}
